/*-
 * SMSDeliver.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 98 $
 * $LastChangedDate: 2007-02-20 15:18:26 +0000 (Tue, 20 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/SMSDeliver.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import java.util.EnumSet;

import com.googlecode.attention.util.Convert;
import com.googlecode.attention.util.IntArrayBuilder;
import com.googlecode.attention.util.KeepingReference;

import static com.googlecode.attention.sms.pdu.PDUElement.*;

/** * SMS-DELIVER. * <BR> * <BR>Basic elements of the SMS-DELIVER type: * <BR> * <BR> * <TABLE BORDER="1"> * <TR><TD>Abbr.</TD><TD>Reference</TD><TD>P1)</TD><TD>R2)</TD><TD>Description</TD></TR> * <TR><TD>TP-MTI</TD><TD>TP-Message-Type-Indicator</TD><TD>M</TD><TD>2b</TD><TD>Parameter describing the message type.</TD></TR> * <TR><TD>TP-MMS</TD><TD>TP-More-Messages-to-Send</TD><TD>M</TD><TD>b</TD><TD>Parameter indicating whether or not there are more messages to send.</TD></TR> * <TR><TD>TP-RP</TD><TD>TP-Reply-Path</TD><TD>M</TD><TD>b</TD><TD>Parameter indicating that Reply Path exists.</TD></TR> * <TR><TD>TP-UDHI</TD><TD>TP-User-Data-Header-Indicator</TD><TD>O</TD><TD>b</TD><TD>Parameter indicating that the TP-UD field contains a Header.</TD></TR> * <TR><TD>TP-SRI</TD><TD>TP-Status-Report-Indication</TD><TD>O</TD><TD>b</TD><TD>Parameter indicating if the SME has requested a status report.</TD></TR> * <TR><TD>TP-OA</TD><TD>TP-Originating-Address</TD><TD>M</TD><TD>2-12o</TD><TD>Address of the originating SME.</TD></TR> * <TR><TD>TP-PID</TD><TD>TP-Protocol-Identifier</TD><TD>M</TD><TD>o</TD><TD>Parameter identifying the above layer protocol, if any.</TD></TR> * <TR><TD>TP-DCS</TD><TD>TP-Data-Coding-Scheme</TD><TD>M</TD><TD>o</TD><TD>Parameter identifying the coding scheme within the TP-User-Data.</TD></TR> * <TR><TD>TP-SCTS</TD><TD>TP-Service-Centre-Time-Stamp</TD><TD>M</TD><TD>7o</TD><TD>Parameter identifying time when the SC received the message.</TD></TR> * <TR><TD>TP-UDL</TD><TD>TP-User-Data-Length</TD><TD>M</TD><TD>I</TD><TD>Parameter indicating the length of the TP-User-Data field to follow.</TD></TR> * <TR><TD>TP-UD</TD><TD>TP-User-Data</TD><TD>O</TD><TD>3).</TD><TD></TD></TR> * </TABLE> * <BR> * <BR>1) Provision; Mandatory (M) or Optional (O). * <BR>2) Representation; Integer (I), bit (b), 2 bits (2b), Octet (o), 7 octets (7o), 2-12 octets (2-12o). * <BR>3) Dependent on the TP-DCS. *  * @author Stein Gunnar Bakkeby */
public class SMSDeliver extends TPDU {
  /* Field values */
  private int fo = 0;
  private Address oa = null; // originating address
  private int pid = 0;
  private int dcs = 0;
  private SCTS scts = null;

  private EnumSet<PDUElement> pduElementSet = EnumSet.of(TP_FO, TP_MTI, TP_MMS,
      TP_RP, TP_UDHI, TP_SRI, TP_OA, TP_TOA, TP_NPI, TP_TON, TP_PID, TP_DCS, TP_SCTS, TP_UDL, TP_UD, 
      TP_UDH, TP_UDHL);
  
  /**
   * Default constructor, creates an empty PDU.
   */
  public SMSDeliver() {
    super(new int[13], TPDU.SMS_DELIVER);
  }

  /**
   * Constructor, creates the SMS-DELIVER based upon the given PDU.
   *
   * @param pdu The raw data
   */
  public SMSDeliver(final String pdu) {
    super(pdu, TPDU.SMS_DELIVER);
  }

  /**
   * Constructor, creates the SMS-DELIVER based upon the given PDU.
   *
   * @param pdu The raw data
   */
  public SMSDeliver(final int[] pdu) {
    super(pdu, TPDU.SMS_DELIVER);
  }

  /**
   * Constructor, creates the SMS-DELIVER.
   *
   * @param fo The first octet
   * @param oa The originating address
   * @param pid The Protocol-Identifier
   * @param dcs The Data-Coding-Scheme
   * @param scts The Service-Centre-Time-Stamp
   * @param ud The User-Data field.
   */
  public SMSDeliver(final FO fo,
                    final Address oa,
                    final PID pid,
                    final DCS dcs,
                    final SCTS scts,
                    final UD ud) {
    this();
    this.fo   = fo.getValue();
    this.oa   = oa;
    this.pid  = pid.getValue();
    this.dcs  = dcs.getValue();
    this.scts = scts;
    this.ud   = ud;
  }

  /**
   * Constructor, creates the SMS-DELIVER without the optional User-Data.
   *
   * @param fo The first octet
   * @param oa The originating address
   * @param pid The Protocol-Identifier
   * @param dcs The Data-Coding-Scheme
   * @param scts The Service-Centre-Time-Stamp
   */
  public SMSDeliver(final FO fo,
                     final Address oa,
                     final PID pid,
                     final DCS dcs,
                     final SCTS scts) {
    this();
    this.fo   = fo.getValue();
    this.oa   = oa;
    this.pid  = pid.getValue();
    this.dcs  = dcs.getValue();
    this.scts = scts;
  }

  /**
   * This will decode the PDU.
   */
  public void decode() {
    resetPDUPointer();
    fo   = extractOctet();
    int oal = extractOctet(); // oa length
    final TOA toa = new TOA(extractOctet());
    int numberOfAddressOctets = ((oal & 1) != 0 ? oal + 1 : oal) / 2;
    if (toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
      //int alength = ((al & 1) == 1 ? al + 1: al) / 2;
      //if (numberOfAddressOctets % 7 == 0) numberOfAddressOctets++;
      //numberOfAddressOctets = (oal - oal % 7) / 2;
    }
    oa   = new Address(oal, toa, extractOctets(numberOfAddressOctets));
    pid  = extractOctet();
    dcs  = extractOctet();
    scts = new SCTS(extractOctets(7));

    final DCS d = new DCS(dcs);
    final int udl = extractOctet();
    int octetsToExtract = udl;
    DCS_GROUP:
    switch (d.getGroup()) {
    case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
      break;

    case DCS.GROUP_GENERAL_DATA_CODING:
    case DCS.GROUP_AUTOMATIC_DELETION:
    case DCS.GROUP_MESSAGE_CODING_CLASS:
      switch (d.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
      case DCS.MESSAGE_CODING_UCS2:
        break DCS_GROUP;

      case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        /* Falls through */
      }
      /* Falls through */

    case DCS.GROUP_RESERVED_1000: /* All reserved groups */
    case DCS.GROUP_RESERVED_1001: /* are assumed to      */
    case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
    case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
    case DCS.GROUP_MESSAGE_WAITING_DISCARD:
    case DCS.GROUP_MESSAGE_WAITING_STORE:
    default:
      // TODO Same problem as with sms submit, if there are more than
      // one header then we don't really know the length of octets to
      // extract unless we have a look at the header length as well
      double res = (double) udl * 7 / 8;
      octetsToExtract = (int) res + (res > (int) res ? 1 : 0); // XXX erm, check
      break;
    }
    ud = new UD(extractOctets(octetsToExtract),   // UD
                udl,                              // UDL
                UDHI.parse(fo),                   // UDHI
                new DCS(dcs));                    // DCS
  }

  /**
   * This will encode the PDU.
   */
  public void encode() {
    /* To avoid NullPointerException */
    if (oa   == null) oa   = new Address();
    if (scts == null) scts = new SCTS();
    if (ud   == null) ud = new UD(new int[0], 0, UDHI.parse(fo), new DCS(dcs));

    /* Begin encoding */
    final IntArrayBuilder output = new IntArrayBuilder(INIT_SIZE);
    output.append(fo);
    output.append(oa.getAddressField());
    output.append(pid);
    output.append(dcs);
    output.append(scts.getVP());
    final int[] udenc = ud.getEncoded();
    output.append(ud.getUDL());
    output.append(udenc);

    if (rest != null && rest.length != 0) {
      output.append(rest);
    }
    
    super.pdu = output;
  }

  /**
   * This will return true if the PDU contains the given type.
   *
   * @param type PDUElement values
   * @see PDUElement
   * @return True if given type is present, false otherwise
   */
  public boolean contains(final PDUElement type) {
    return pduElementSet.contains(type);
  }

  /**
   * This will return an element of a given type.
   * 
   * @param type PDUElement values
   * @see PDUElement
   * @return The element in this TPDU
   */
  public int get(final PDUElement type) {
    switch (type) {
    case TP_PID:  return pid;
    case TP_DCS:  return dcs;
    case TP_FO:   return fo;
    case TP_TOA:  return (oa == null ? 0 : oa.getTOA().getValue());
    case TP_TON:  return (oa == null ? 0 : oa.getTOA().getTypeOfNumber());
    case TP_NPI:  return (oa == null ? 0 : oa.getTOA().getNumPlanIdent());
    case TP_MTI:  return new FO(fo).get(TP_MTI);
    case TP_MMS:  return new FO(fo).get(TP_MMS);
    case TP_RP:   return new FO(fo).get(TP_RP);
    case TP_UDHI: return new FO(fo).get(TP_UDHI);
    case TP_SRI:  return new FO(fo).get(TP_SRI);
    case TP_UDL:  return ud.getUDL();
    
    default:
      return -1;
    }
  }
  
  /**
   * This will return the originating address.
   * 
   * @return The TP-OA
   */
  public Address getOriginatingAddress() {
    return oa;
  }
  
  /**
   * This will return the service centre time stamp.
   * 
   * @return The TP-SCTS
   */
  public SCTS getServiceCentreTimeStamp() {
    return scts;
  }
  
  /**
   * This will convert the user data into properly readable data.
   * 
   * @return The TP-UD
   */
  public String getMessage() {
    return new String(Convert.intToChar(getUserData().toArray()));
  }
  
  /**
   * This will set an element of a given type.
   * 
   * @param type See static field values
   * @param value The value of the element to set
   * @return True if PDU contains the given type, false otherwise
   */
  public boolean set(final PDUElement type, final int value) {
    switch (type) {
    case TP_PID:
      pid = value;
      break;
      
    case TP_DCS:
      dcs = value;
      break;
      
    case TP_FO:
      fo = value;
      break;
      
    case TP_MTI:
    case TP_MMS:
    case TP_RP:
    case TP_UDHI:
    case TP_SRI:
      final FO f = new FO(fo);
      f.set(type, value);
      fo = f.getValue();
      break;
      
    default:
      return false;
    }
    return true;
  }
  
  /**
   * This will set the service centre time stamp.
   * 
   * @param timestamp The SCTS of this SMS deliver.
   */
  @KeepingReference
  public void setServiceCentreTimeStamp(final SCTS timestamp) {
    this.scts = new SCTS(timestamp.getVP());
  }
  
  /**
   * This will set the originating address.
   * 
   * @param address The OA of this SMS deliver.
   */
  @KeepingReference
  public void setOriginatingAddress(final Address address) {
    this.oa = address;
  }
  
  /**
   * This will return the originators address. The method is equivalent of
   * the getOriginatingAddress() method.
   * 
   * @return TP-OA
   * @see #getOriginatingAddress()
   */
  @Override
  public Address getAddress() {
    return getOriginatingAddress();
  }
  
  /**
   * This will set the originators address. The method is equivalent of
   * the setOriginatingAddress() method.
   * 
   * @see #setOriginatingAddress(Address)
   */
  @Override
  public void setAddress(final Address address) {
    setOriginatingAddress(address);
  }
  
  /**
   * @return [FO:&lt;fo&gt;][OA:&lt;address&gt;][PID:&lt;pid&gt;][DCS:&lt;dcs&gt;][SCTS:&lt;scts&gt;][UD:&lt;ud&gt;]
   */
  public String toString() {
    this.decode();
    final StringBuilder sb = new StringBuilder(256);
    sb.append("[FO:");
    sb.append(fo); // TODO fix fo
    sb.append("][OA:");
    sb.append(oa);
    sb.append("][PID:"); // TODO fix pid    
    sb.append(pid);
    sb.append("][DCS:"); // TODO fix dcs
    sb.append(dcs);
    sb.append("][SCTS:");
    sb.append(scts); // TODO fix scts
    sb.append("][UD:");
    sb.append(ud);
    sb.append(']');
    return sb.toString();
  }

  public int compareTo(final TPDU another) {
    if (another instanceof SMSDeliver) {
      final SMSDeliver sms = (SMSDeliver)another;
      if (this.scts == null) {
        return super.compareTo(another);
      }
      return this.scts.compareTo(sms.getServiceCentreTimeStamp());
    }
    return -1;
  }
}